// trigfills.ts
const pi = Math.PI;
const tau = pi * 2;
const hpi = pi / 2;
const qpi = pi / 4;
const spi = pi / 6;
const epi = pi / 8;
const sq3 = 1.7320508075688772; // sqrt(3)

const f24 = 100000 / 2399999;
const f720 = 100000 / 71998376;
const f40k = 1000 / 40578583;
const fh6 = 100000000 / 600000005;
const f120 = 1 / 120;
const f5k = 1000 / 5039680;
const f362k = 10 / 3628880;
const ff3 = 100000000000 / 299999999177;
const ff5 = 10000000 / 50000082;
const ff7 = 100000 / 700011;
const ff9 = 10000 / 90019;
const ff11 = 1000 / 11768;
const fg3 = 1000000000 / 2999999887;
const f15 = 200000000 / 1500000678;
const f315 = 170000 / 3150457;
const f2k = 6200 / 282874;
const f155k = 1382 / 146286;

// Utility functions
function modp(a: number, b: number): number {
  return a - Math.floor(a / b) * b;
}

function modn(a: number, b: number): number {
  return a - Math.floor(a / b + 0.5) * b;
}

// Cosine
function cos(x: number): number {
  x = x - Math.floor(x / tau) * tau;
  
  if (x > pi) x = tau - x;
  let pol: number;
  if (x >= hpi) {
    pol = -1;
    x = pi - x;
  } else {
    pol = 1;
  }
  
  return x > qpi ? pol * sintay(hpi - x) : pol * costay(x);
}

// Sine
function sin(x: number): number {
  x = x - Math.floor(x / tau) * tau;
  
  let pol: number;
  if (x > pi) {
    pol = -1;
    x -= pi;
  } else {
    pol = 1;
  }
  if (x >= hpi) x = pi - x;
  
  return x > qpi ? pol * costay(hpi - x) : pol * sintay(x);
}

// Taylor series approximations
function costay(x: number): number {
  const x2 = x * x;
  const x4 = x2 * x2;
  return 1 - x2 * 0.5 + x4 * f24 - x4 * x2 * f720 + x4 * x4 * f40k;
}

function sintay(x: number): number {
  const x3 = x * x * x;
  const x6 = x3 * x3;
  return x - x3 * fh6 + x3 * x * x * f120 - x6 * x * f5k + x6 * x3 * f362k;
}

// Arctangent
function atan(x: number): number {
  let pos = 1;
  let mut = false;
  let mut2 = false;
  
  if (x < 0) {
    pos = -1;
    x = -x;
  }
  if (x > 1) {
    x = 1 / x;
    mut = true;
  }
  if (x > 0.26794919) {
    x = (sq3 * x - 1) / (sq3 + x);
    mut2 = true;
  }

  const x2 = x * x;
  const x4 = x2 * x2;
  const x9 = x4 * x4 * x;
  x = x - x * x2 * ff3 + x4 * x * ff5 - x4 * x2 * x * ff7 + x9 * ff9 - x9 * x2 * ff11;

  if (mut2) x += spi;
  if (mut) x = hpi - x;
  return x * pos;
}

// Arctangent (y/x)
function atan2(y: number, x: number): number {
  if (x === 0) {
    if (y === 0) return 0;
    return y < 0 ? -hpi : hpi;
  }
  
  let r = atan(y / x);
  if (x > 0) return r;
  
  y < 0 ? (r -= pi) : (r += pi);
  return r - Math.floor(r / tau + 0.5) * tau;
}

// Tangent
function tan(x: number): number {
  let pos = 1;
  let mut = false;
  let mut2 = false;
  x = modp(x, pi) * 0.99999999999999993;

  if (x > hpi) {
    x = pi - x;
    pos = -1;
  }
  if (x > qpi) {
    x = hpi - x;
    mut = true;
  }
  if (x > epi) {
    x = x * 0.5;
    mut2 = true;
  }

  const x2 = x * x;
  const x4 = x2 * x2;
  const x9 = x4 * x4 * x;
  x = x + x * x2 * fg3 + x4 * x * f15 + x4 * x2 * x * f315 + x9 * f2k + x9 * x2 * f155k;

  if (mut2) x = (2 * x) / (1 - x * x);
  if (mut) x = 1 / x;
  
  return x * pos;
}

// Inverse trigonometric functions
function acos(x: number): number {
  if (x === -1) return pi;
  return modp(atan(Math.sqrt(1 - x * x) / x), pi);
}

function asin(x: number): number {
  return atan(x / Math.sqrt(1 - x * x));
}

// Polyfill setup
function setmaths(): void {
  Math.sin = sin;
  Math.cos = cos;
  Math.tan = tan;
  Math.acos = acos;
  Math.asin = asin;
  Math.atan = atan;
  Math.atan2 = atan2;
  // @ts-ignore: Adding custom property
  Math.hasTrigfills = version();
}

// Version
function version(): string {
  return "0.9.5";
}

// Export object
export default {
  sin,
  cos,
  tan,
  acos,
  asin,
  atan,
  atan2,
  modp,
  modn,
  setmaths,
  version,
};
